﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.IO;
using System.Collections;
using System.Xml.Serialization;
using System.Data;

namespace SSISFromCode.Helpers
{

    /// <summary>
    /// What's the quickest and easiest way to generate an XML schema?  Loading the XML data into a DataSet and calling it's WriteXmlSchema method is.
    /// I'm sure there's a more official way of doing it, but I damn sure couldn't find it.
    /// Also, the DataSet makes all data types in the schema string.  This is exactly what we want because our destination table is all typed to string.
    /// Any other types would fail in the downstream data conversion component.
    /// 
    /// See "performance" comment below.
    /// </summary>
    internal class SchemaGenerator
    {

        private List<string> _nodeNames = new List<string>();

        public bool Generate( string xmlInPath, string xmlOutPath )
        {
            var result = false;
            
            try
            {
                if ( File.Exists(xmlInPath) )
                {
                    /// Note: At this point I could simply load the xml from file into the DataSet below.
                    /// I'm going through these handstands for performance: if the file is large, loading it all into the DataSet
                    /// is time consuming and unnecessary.
                    using ( var reader = XmlReader.Create( xmlInPath ) )
                    {
                        using ( var sr = new StringWriter() )
                        {
                            using ( var writer = XmlWriter.Create( sr ) )
                            {
                                WritePortion( reader, writer );
                                writer.Flush();

                                /// Why is this so hard?  Why do I have to use 4 different objects to load this string?  And why did a MemoryStream JUST FLAT NOT WORK?
                                using ( var whyDoIHaveToUseAnotherReader = new StringReader( sr.ToString() ) )
                                {
                                    var ds = new DataSet();
                                    ds.ReadXml( whyDoIHaveToUseAnotherReader );
                                    SetColumnLengths( ds );
                                    ds.WriteXmlSchema( xmlOutPath );
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
            catch ( Exception )
            {
                ;
            }

            return result;
        }

        /// <summary>
        /// In the SSIS designer, if you slap down an XML Source component, set it's XML source file property, choose the option to generate a schema, then click on the "Columns" section
        /// of the XML Source component, you will get an error for each column (element) in the XML schema (and if there are lots of columns, it may be a message box many times 
        /// larger than your monitor) complaining about the data type and length missing.
        /// We will get that same flood of messages in our ImportDirector.GeneratedMessages collection here unless we do this.  Though they cause no actualy problems because downstream from
        /// the component we set lengths and types, in the event of debugging, those messages would just be a major distraction.
        /// </summary>
        /// <param name="ds"></param>
        private void SetColumnLengths( DataSet ds )
        {
            foreach ( DataColumn col in ds.Tables[0].Columns )
            {
                col.MaxLength = DESTINATION_TABLE.CHAR_WIDTH;
            }
        }

        private void WritePortion( XmlReader r, XmlWriter w )
        {
            while ( r.Read() )
            {
                if ( r.NodeType == XmlNodeType.Element )
                {
                    _nodeNames.Add( r.Name );
                }

                if ( IsRepeatingElem( r ) )
                {
                    for ( int i = 0; i < r.Depth; i++ )
                    {
                        w.WriteEndElement();
                    }
                    return;
                }
                else
                {
                    Write( r, w );
                }
            }
        }

        /// <summary>
        /// The repeating element should equate to the rows we want to import.
        /// </summary>
        /// <param name="r"></param>
        /// <returns></returns>
        private bool IsRepeatingElem( XmlReader r )
        {
            return _nodeNames.Where( n => n == r.Name ).Count() > 10;  // 10: top sampling.  Could be some optional nodes?
        }

        private void Write( XmlReader reader, XmlWriter writer )
        {
            switch ( reader.NodeType )
            {
                case XmlNodeType.Element:
                    writer.WriteStartElement( reader.Prefix, reader.Name, reader.NamespaceURI );
                    writer.WriteAttributes( reader, true );
                    break;
                case XmlNodeType.Text:
                    writer.WriteString( reader.Value );
                    break;
                case XmlNodeType.EndElement:
                    writer.WriteFullEndElement();
                    break;
                case XmlNodeType.XmlDeclaration:
                case XmlNodeType.ProcessingInstruction:
                    writer.WriteProcessingInstruction( reader.Name, reader.Value );
                    break;
                case XmlNodeType.SignificantWhitespace:
                    writer.WriteWhitespace( reader.Value );
                    break;
            }
        }

    }

}